﻿//#define _CRT_SECURE_NO_WARNINGS
//#include <stdio.h>
#include "common.h"

/*
整数在内存中存放的是补码，原因如下：
使用补码，可以将符号位和数值域统一处理，同时，加法和减法运算可以统一处理（CPU只有加法器）
    此外，补码和原码相互转换的操作相同，不需要额外的硬件电路
    例如：1 - 1 可以写为 1 + (-1)
    补码计算过程：
        1 的补码为   00000000 00000000 00000000 00000001
        -1 的补码为  11111111 11111111 11111111 11111111
        加和为      100000000 00000000 00000000 00000000   （33位）
        截断后为     00000000 00000000 00000000 00000000   值为 0
    补码可以按同样的方式得到原码：
          -1的补码   11111111 11111111 11111111 11111111
            取反为   10000000 00000000 00000000 00000000    （注意符号位不变）
            加一为   10000000 00000000 00000000 00000001
    unsigned char -1 ----> int 255，数据整型提升是先读取在截断，与大小端无关
    char -128 ----> unsigned int 4294967168，对应 signed int -128 过程如下：
        char -128 ----> 10000000  补码
        整型提升 ---->   11111111 11111111 11111111 10000000  补码，无符号则也为原码
        取反为    ---->  10000000 00000000 00000000 11111111  补码的反码，符号位不变
        加一为    ---->  10000000 00000000 00000001 00000000  有符号原码，对应 -128
    char 128 同 char -128 因为补码一样

 所谓大端模式（Big Endian）就是指数据的低位字节保存在内存的高地址中，
    而数据的高位字节保存在内存的低地址中。
    比如 0x11 22 33 44（注意 44 是最低位）存放顺序为 11 22 33 44
小端模式（Little Endian）就是指数据的高位字节保存在内存的高地址中，
    而数据的低位字节保存在内存的低地址中。（VS调试时看到的内存数值存放顺序）
    比如 0x11 22 33 44（注意 11 是最高位）存放顺序为 44 33 22 11

根据 IEEE 标准，任何一个浮点数 f 都可以用以下形式来表示(^ 表示乘方)：
    (-1)^S * M * 2^E
    (-1)^S 表示符号位, 当 S = 0 时，表示正数，S = 1 时，表示负数
    M 表示有效数字（小数）, 大于等于 1 且小于 2
    2^E 的 E 表示指数位。

    一个例子：9.5f 先转成二进制为 1001.10
        小数点后面第一位权重为 2^-1，第二位权重为 2^-2...
        所以这里 9.5 = 1*2^3 + 0*2^2 + 0*2^1 + 1*2^0 + 1*2^-1 + 0*2^-2
        1001.10 写为      1.00110 * 2^3
        表示为 IEEE 标准为 (-1)^0 * 1.00110 * 2^3
        那么上述标准的字母为  S = 0, M = 1.00110, E = 3
    另一个例子：0.5f 先转成二进制为 0.10
        这里 0.5 = 0*2^0 + 1*2^-1
        写为 0.10 等同于  1.0 * 2^-1
        IEEE 标准为      (-1)^0 * 1.0 * 2^-1
        字母为           S = 0, M = 1.0, E = -1

    有时候，浮点数无法在内存中精确表示，比如 9.1f 无法用 IEEE 标准精确表示，理由：
        9.1f 转成二进制为 1001.000110011001101...

IEEE 754 规定:
    对于 32 位的浮点数, 最高的 1 位是符号位 S, 接着的 8 位是指数 E, 剩下的 23 位为有效数字 M。
    对于 64 位的浮点数, 最高的 1 位是符号位 S, 接着的 11 位是指数 E, 剩下的 52 位为有效数字 M。
IEEE 754 对有效数字M和指数E,还有一些特别规定:
    前面说过, 1≤M<2, 也就是说, M可以写成 1.xxxxxx 的形式, 其中 xxxxxx 表示小数部分。
IEEE 754 规定, 在计算机内部保存M时, 默认这个数的第一位总是 1, 因此可以被舍去, 只保存后面的
    xxxxxx 部分。比如保存 1.01 的时候, 只保存 01, 等到读取的时候, 再把第一位的 1 加上去。
    这样做的目的,是节省 1 位有效数字。以 32 位浮点数为例, 留给 M 只有 23 位, 而
    将第一位的 1 舍去以后, 等于可以保存 24 位有效数字。
    那么，E 部分的取值范围是多少呢？
    首先，E 是一个无符号整数，这就意味着，如果 E 是 8 位的，那么它的取值范围就是 0~255
    E 是 11 位的，那么它的取值范围就是 0~2047，由于科学计数法中的指数部分可能为负数，
    所以，IEEE 754规定，E 存入内存时，必须在加上一个中间值，8 位的中间值是 127，
    11 位的中间值是 1023。比如 32 位的 (-1)^0 * 1.0 * 2^-1 存储 E 的值是 127 - 1 = 126，
    在比如 32 位的 (-1)^1 * 1.0 * 2^10 存储 E 的值是 127 + 10 = 137
IEEE 754 规定，对于浮点数的读取，分三种情况：
    正常情况：指数 E 存放的二进制地址不为全零，也不为全 1，则按照正常的浮点数格式读取。
        即，先读取指数有效数字 M 的值加上第一位的 1，然后根据指数 E 计算出对应的数值
        （比如 10000010 ---> 130 - 127 = 3）, 再根据符号位 S 确定正负号。
        0 10000010 00110 000000000000000000 读取为 (-1)^0 * 1.00110 * 2^3 = 9.5
    特殊情况一：指数 E 存放的二进制地址全为 1，则表示无穷大的数，正负由符号位 S 决定。
    特殊情况二：指数 E 存放的二进制地址全为 0，则表示无限接近 0，正负由符号位 S 决定，
        此时读取有效数字 M 前面直接补 0，表示数值非常接近 0

总结：


*/

extern void test_y2y();

int main()
{
    // 判断数据在内存中存放的顺序（大端还是小端）
    {
        print_start_line("判断数据在内存中存放的顺序测试开始");
        int a1 = 1;
        char* pca1 = (char*)&a1;
        if (pca1[3] == a1)
            printf("大端模式\n");
        else
            printf("小端模式\n");
        char a2 = -128;
        printf("unsigned char a2 = %u\n", a2);  // 4294967168   %u 表示无符号整数
        printf("signed char a2 = %d\n", a2);  // 4294967168   %u 表示无符号整数
        printf("sizeof(long double) = %lld\n", sizeof(long double));

        print_end_line("判断数据在内存中存放的顺序测试结束");
    }

    // 整数存储典型示例
    {
        print_start_line("整数存储典型示例部分开始");
        // unsigned 负数打印测试
        //printf_unsigned();  // 死循环打印 9、8 ~ 4294967295、4294967294、9、8...

        // strlen 理解
        printf("sizeof arr[1000] = %d\n", strlen_test());

        print_end_line("整数存储典型示例部分结束");
    }

    // 浮点数在内存中的存放
    {
        print_start_line("浮点数在内存中的存放测试开始");
        // 整数类型的头文件为 limits.h 浮点数的头文件为 float.h
        printf_float();  // 说明浮点数与整数的存储方式不同

        // 浮点数的存储方式
        float f1 = 9.5f; // 无 f 默认是 double 这里是类型转换，不是截断(截断只针对整型)
        float* pf1 = &f1;
        /* 调试观察内存时可以发现：
            9.5f 先转成二进制为 1001.10
            写为 IEEE 标准为 (-1)^0 * 1.00110 * 2^3 这里 S = 0, M = 1.00110, E = 3
            按照 IEEE 754 标准 S = 0, M = 00110, E = 127 + 3 = 130 (130 = 10000010)
            存储为二进制为：0 10000010 00110 000000000000000000
            将上面二进制按照 4 位一组转 16 进制为：0x 41 18 00 00
            按照小端模式，内存中存放的顺序为：0x 00 00 18 41
        */
        printf("float f1 = %f\n", f1);

        print_end_line("浮点数在内存中的存放测试结束");
    }

    // 作业部分内容
    {
        print_start_line("作业部分内容开始");
        // 写一个函数，将整型数组中的奇数放在前面，偶数放在后面
        int arr1[15] = { 60, 1, 10, 9, 3, 24, 2, 4, 5, 6, 7, 8, 26, 25, 23 };
        odd_even_sort1(arr1, 0, 14);  // 数组长度为 10
        int i;
        for (i = 0; i < 15; i++)
            printf("%d ", arr1[i]);
        printf("\n");

        int arr2[15] = { 60, 1, 10, 9, 3, 24, 2, 4, 5, 6, 7, 8, 26, 25, 23 };
        odd_even_sort2(arr2, 0, 14);  // 数组长度为 10
        for (i = 0; i < 15; i++)
            printf("%d ", arr2[i]);
        printf("\n");

        // 写一个函数，合并两个有序数组，并返回合并后的数组
        int arr3[15] = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 25, 27, 29, 31, 33 };
        int arr4[14] = { 2, 4, 6, 8, 10, 11, 14, 16, 18, 20, 22, 26, 36, 38 };
        int* arr3_and_ar4 = two_sorted_arr_merge(arr3, 15, arr4, 14); // 注意参数顺序
        for (i = 0; i < 29; i++)
            printf("%d ", arr3_and_ar4[i]);
        printf("\n");

        print_end_line("作业部分内容结束");
    }

    // 文件操作测试
    //file_test();
    test_aes();
    test_file_size();

    // 测试自写异或加解密
    print_start_line("自写异或加解密测试开始");
    test_y2y();
    print_end_line("自写异或加解密测试结束");

    return 0;
}

